import pdb
import os
import sys
import gc
import platform
import ccxt
import traceback
import random
import time
import importlib
import numpy as np
import pandas as pd
from datetime import datetime, timedelta
from concurrent.futures import ThreadPoolExecutor

from alphalib.backtest.playback import timer
from alphalib.product.exchange_config import exchange_basic_config
from alphalib.config_product import is_debug, account_config, njob, glob_var, auto_reb
from alphalib.product.api.trade import cancel_spot_all_orders, cancel_swap_all_orders
from alphalib.product.api.market import check_position_side, check_multi_assets_margin
from alphalib.product.api.order import proceed_spot_order, proceed_swap_order
from alphalib.product.utils.commons import sleep_until_run_time, remedy_until_run_time
from alphalib.product.utils.datatools import check_flag, load_raw_data
from alphalib.product.utils.functions import refresh_diff_time, del_insufficient_data, \
    update_account_info, calc_alloc_ratio, cal_order_amount, save_and_merge_select, save_select_coin, save_symbol_order
from alphalib.product.utils.statistics import record_before_order_pos, record_after_order_pos
from alphalib.product.utils.notify import send_import_ding_message
from alphalib.contrib.strategy.z1_playback import trans_spot_to_swap, Z1PlaybackConfig


# 解决pm2在win系统下的乱码问题
os_type = platform.system()
if os_type == 'Windows':  # win执行
    sys.stdout.reconfigure(encoding='utf-8')
    # 若上面sys.stdout = ... 这一行不行，可以试试下一行
    # import io
    # sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding='utf-8')

# 构建线程池
executor = ThreadPoolExecutor()
# 获取当前环境下的python解释器
python_exec = sys.executable
# 获取数据的公共交易所对象
common_exchange = ccxt.binance(exchange_basic_config)


# 更新账户信息及策略
def update_account_strategy(account_config):
    current_time = datetime.now()
    # 将分钟和秒数设为零，即向下取整到上一个整点
    now_time = current_time.replace(minute=0, second=0)

    # (回测代码里有，先保留)；格式化时间为字符串
    start_date = (now_time - timedelta(hours=240)).strftime("%Y-%m-%d %H:%M:%S")
    end_date = (now_time + timedelta(hours=24)).strftime("%Y-%m-%d %H:%M:%S")

    # ===== 获取账户信息
    update_account_config = {}

    has_spot_strategy = False
    has_swap_strategy = False
    for account_name, account_info in account_config.items():
        # 任何一个账户使用了现货或合约，数据中心就需要获取数据
        if_use_spot = account_info['has_spot_strategy']
        if if_use_spot:
            has_spot_strategy = if_use_spot
        if_use_swap = account_info['has_swap_strategy']
        if if_use_swap:
            has_swap_strategy = if_use_swap
        
        account_info = update_account_info(account_name, account_info)
        # account_info = account_info.copy()
        if not account_info:
            continue

        strategy_name = account_info['strategy_name']
        strategy_module = importlib.import_module(f'strategies.{strategy_name}')
        stg = strategy_module.strategy
        z1_conf = Z1PlaybackConfig(
            compound_name=strategy_name,
            start_date=start_date,
            end_date=end_date,
            leverage=1,          # 杠杆建议定死在 1
            enable_funding_rate=False
        )
        account_info['strategy'] = stg
        account_info['strategy_conf'] = z1_conf

        update_account_config[account_name] = account_info

    return update_account_config, has_spot_strategy, has_swap_strategy


def run():
    while True:
        # =====sleep直到该小时开始。但是会随机提前几分钟。
        if not is_debug:  # 非调试模式，需要正常进行sleep
            random_time = random.randint(1 * 60, 2 * 60)  # 不建议使用负数。小时级别提前时间可以设置短一点
            run_time = sleep_until_run_time('1h', if_sleep=True, cheat_seconds=random_time)  # 每小时运行
        else:  # 调试模式，不进行sleep，直接继续往后运行
            random_time = None
            # run_time = sleep_until_run_time('1h', if_sleep=False, cheat_seconds=0)  # 每小时运行
            # 以下代码可以测试的时候使用(UTC0点 日K才走完，国内时间对应是 早上8点)
            # run_time = datetime.strptime('2024-01-28 09:00:00', "%Y-%m-%d %H:%M:%S")
            # 获取当前时间
            current_time = datetime.now()
            # 将分钟和秒数设为零，即向下取整到上一个整点
            new_time = current_time.replace(minute=0, second=0)
            # 格式化时间为字符串
            formatted_time = new_time.strftime("%Y-%m-%d %H:%M:%S")
            run_time = datetime.strptime(formatted_time, "%Y-%m-%d %H:%M:%S")

        # =====判断指数的本地文件有没有更新成功。
        flag = check_flag(run_time)
        # 判断数据是否更新好了
        if not flag and not is_debug:  # 没有更新好，跳过当前下单
            continue

        # =====获取账户信息
        update_account_info, has_spot_strategy, has_swap_strategy = update_account_strategy(account_config)
        # 判断是否没能成功读取任一账户
        if not len(update_account_info.keys()):  # 如果update_account_info数据为空，表示更新账户信息失败
            print('所有账户更新信息失败，60s后重试······')
            time.sleep(60)
            continue

        print('开始获取实时数据')
        # ===读取实盘所需数据
        with timer.timer('--fetch data'):
            # 读取数据中心数据
            symbol_swap_candle_data = dict()
            if has_swap_strategy:
                symbol_swap_candle_data = load_raw_data('swap', symbol_candle_data=symbol_swap_candle_data, run_time=run_time, n_jobs=njob)
                # ===删除一些数据不全的币种
                symbol_swap_candle_data = del_insufficient_data(symbol_swap_candle_data)
            symbol_spot_candle_data = dict()
            if has_spot_strategy:
                symbol_spot_candle_data = load_raw_data('spot', symbol_candle_data=symbol_spot_candle_data, run_time=run_time, n_jobs=njob)
                # ===删除一些数据不全的币种
                symbol_spot_candle_data = del_insufficient_data(symbol_spot_candle_data)
            all_spot_df, all_swap_df = trans_spot_to_swap(all_spot_df=symbol_spot_candle_data, all_swap_df=symbol_swap_candle_data)
        
        for account_name, account_info in update_account_info.items():
            # 获取账号的配置
            account_info['account_name'] = account_name  # 账户信息里面，追加一下用户名称
            exchange = account_info['exchange']  # 交易所对象
            has_spot_strategy = account_info['has_spot_strategy']  # 是否含有现货策略
            spot_position = account_info['spot_position']  # 现货持仓
            swap_position = account_info['swap_position']  # 合约持仓
            hour_offset = account_info['hour_offset']  # 分钟偏移

            if run_time.minute != int(hour_offset[:-1]):
                print(f'账号：{account_name}，当前不是需要下单的时间，跳过···')
                continue

            # 下单前记录持仓
            record_before_order_pos(exchange, run_time, account_name, has_spot_strategy)

            # ===撤销所有币种挂单
            # 合约撤单
            cancel_swap_all_orders(exchange)
            # 现货撤单
            cancel_spot_all_orders(exchange)

            stg = account_info['strategy']
            stg_conf = account_info['strategy_conf']
            # 得到策略相应选币
            with timer.timer('calc alloc_ratio'):
                select_coin = calc_alloc_ratio(stg, stg_conf, all_spot_df=all_spot_df, all_swap_df=all_swap_df, n_jobs=njob)
            print('选币结果：\n', select_coin)

            # 如果选币数据为空，并且当前账号没有任何持仓，直接跳过后续操作
            if select_coin.empty and spot_position.empty and swap_position.empty:
                continue
            # ===保存并合并本地选币文件
            select_coin = save_and_merge_select(select_coin, account_name, account_info, auto_reb=auto_reb)

            # ===开始计算具体下单信息
            symbol_order = cal_order_amount(select_coin, account_info)
            print('下单信息：\n', symbol_order)
            total_abs_sum = symbol_order['实际下单资金'].abs().sum()
            print('下单总金额：\n', total_abs_sum)
            # 调试模式，打印下单信息之后即可退出程序
            if is_debug:
                print('Debug 模式，不下单')
                continue

            # ===下单
            proceed_spot_order(exchange, symbol_order, account_info, select_coin, is_only_sell=True)
            proceed_swap_order(exchange, symbol_order, account_info)
            proceed_spot_order(exchange, symbol_order, account_info, select_coin, is_only_sell=False)

            # ===保存选币数据
            save_select_coin(select_coin, run_time, account_name)

            # ===保存下单数据
            save_symbol_order(symbol_order, run_time, account_name)

            # 记录下单后持仓
            record_after_order_pos(exchange, run_time, account_name, has_spot_strategy)
            print('结束操作时间：', datetime.now().strftime("%Y-%m-%d %H:%M:%S"))


        # 本次循环结束
        print('-' * 20, '本次循环结束，%f秒后进入下一次循环' % 20, '-' * 20)
        print('\n')
        time.sleep(20)

        # =====自动同步时间
        # 当前服务器时间与交易所时间偏差超过60s
        if glob_var.diff_timestamp / 1000 > 60:
            refresh_diff_time(common_exchange)
            if os_type == 'Windows':  # win执行
                msg = '当前服务器时间与交易所时间偏差超过60s, win用户请手动运行 sync_tim.py'
                print(msg)
                send_import_ding_message(msg)
            elif os_type == 'Linux' or os_type == 'Darwin':  # linux和mac执行
                from sync_time import sync_time_ntp
                sync_time_ntp()  # 指定更新时间
                refresh_diff_time(common_exchange)

        # =====清理数据
        del update_account_info, flag
        gc.collect()  # 强制执行回收一下

        # 补偿当前时间与run_time之间的差值
        remedy_until_run_time(run_time)


if __name__ == '__main__':
    common_exchange = ccxt.binance(exchange_basic_config)
    # =====刷新与交易所的时差
    refresh_diff_time(common_exchange)
    # =====遍历账号进行调整
    for name, account in account_config.items():
        # ===检查并且设置持仓模式：单向持仓
        check_position_side(account['exchange'])
        # ===检查联合保证金模式
        check_multi_assets_margin(account['exchange'])
        # ===多账户之间，停顿一下
        time.sleep(5)
    while True:
        try:
            run()
        except Exception as err:
            msg = '系统出错，10s之后重新运行，出错原因: ' + str(err)
            print(msg)
            print(traceback.format_exc())
            send_import_ding_message(msg)
            time.sleep(10)
